/*
 * topological-hex, a program to compute combinatorial hexahedral meshes.
 *
 * Copyright (C) <2018> <Université catholique de Louvain (UCL), Belgique>
 *
 * List of the contributors to the development, description and complete
 * License: see LICENSE file.
 *
 * This program (topological-hex) is free software:
 * you can redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (see COPYING file).  If not,
 * see <http://www.gnu.org/licenses/>.
 */


#include <check.h>
#include <stdlib.h>

#include "vertex_set.h"

static int compare_uint32(const void *a, const void *b) {
  uint32_t a_val = *(uint32_t*)a;
  uint32_t b_val = *(uint32_t*)b;
  if (a_val > b_val) return 1;
  else if (a_val == b_val) return 0;
  else return -1;
}

static
void check_contents(const vertex_set *set,
                    uint32_t *values, uint32_t n) {
  qsort(values, n, sizeof(*values), compare_uint32);

  uint32_t i = 0;
  VERTEX_SET_FOREACH(*set, v) {
    ck_assert_int_lt(i, n);
    ck_assert_int_eq(v, values[i]);
    i++;
  }
  ck_assert_msg(i == n, "Set contains %u elements, was expected to contain %u",
                i, n);

  i = 0;
  for (uint32_t j = 0; j < VERTEX_SET_MAX; j++) {
    if (i < n && j == values[i]) {
      ck_assert(vertex_set_contains(set, j));
      i++;
    }
    else
      ck_assert(!vertex_set_contains(set, j));
  }

  ck_assert_msg(vertex_set_size(set) == n,
                "Set claims to contain %u elements, was expected to contain %u",
                vertex_set_size(set), n);

  if (n == 1) {
    ck_assert_msg(vertex_set_unit(set),
                  "Set reports to be of size one but vertex_set_unit"
                  " returns false");
  }
  else {
    ck_assert_msg(!vertex_set_unit(set),
                  "Set reports to be of size different from one but "
                  "vertex_set_unit returns true");
  }

  if (n != 0)
    ck_assert(vertex_set_min(set) == values[0]);
}

START_TEST(test_init_full) {
  vertex_set set;
  vertex_set_init_full(&set);

  for (uint32_t i = 0; i < VERTEX_SET_MAX; i++)
    ck_assert(vertex_set_contains(&set, i));

  uint32_t size = 0;
  VERTEX_SET_FOREACH(set, x) {
    ck_assert_int_eq(x, size);
    size++;
  }

  ck_assert_int_eq(size, VERTEX_SET_MAX);
  ck_assert_int_eq(vertex_set_size(&set), VERTEX_SET_MAX);
} END_TEST

START_TEST(test_init_empty) {
  vertex_set set;
  vertex_set_init_empty(&set);

  for (uint32_t i = 0; i < VERTEX_SET_MAX; i++)
    ck_assert(!vertex_set_contains(&set, i));

  VERTEX_SET_FOREACH(set, x) {
    ck_abort_msg("Found element %u inside a set meant to be empty.",
                 (uint32_t)x);
  }

  ck_assert_int_eq(vertex_set_size(&set), 0);
} END_TEST

START_TEST(test_init_up_to) {
  for (uint32_t max = 0; max < VERTEX_SET_MAX; max++) {
    vertex_set set;
    vertex_set_init_up_to(&set, max);

    for (uint32_t i = 0; i < max; i++)
      ck_assert(vertex_set_contains(&set, i));
    for (uint32_t i = max; i < VERTEX_SET_MAX; i++)
      ck_assert(!vertex_set_contains(&set, i));

    uint32_t size = 0;
    VERTEX_SET_FOREACH(set, x) {
      ck_assert_int_eq(x, size);
      size++;
    }

    ck_assert_int_eq(size, max);
    ck_assert_int_eq(vertex_set_size(&set), max);
  }
} END_TEST

START_TEST(test_insert_remove) {
  vertex_set set;
  vertex_set_init_empty(&set);

  ck_assert(vertex_set_insert(&set, 3));
  ck_assert(vertex_set_insert(&set, VERTEX_SET_MAX-1));
  ck_assert(vertex_set_insert(&set, VERTEX_SET_MAX/2));
  ck_assert(vertex_set_insert(&set, 52));
  ck_assert(!vertex_set_insert(&set, VERTEX_SET_MAX/2));
  ck_assert(!vertex_set_insert(&set, VERTEX_SET_MAX-1));
  ck_assert(vertex_set_insert(&set, VERTEX_SET_MAX/2-1));
  ck_assert(vertex_set_insert(&set, 0));

  {
    uint32_t contents[] = {0, 3, 52, VERTEX_SET_MAX/2-1, VERTEX_SET_MAX/2,
                           VERTEX_SET_MAX-1};
    check_contents(&set, contents, sizeof(contents)/sizeof(*contents));
  }

  ck_assert(vertex_set_remove(&set, VERTEX_SET_MAX/2-1));
  ck_assert(vertex_set_remove(&set, 0));
  ck_assert(!vertex_set_remove(&set, 0));
  ck_assert(!vertex_set_remove(&set, 49));
  ck_assert(!vertex_set_remove(&set, 49));
  ck_assert(vertex_set_remove(&set, 52));

  {
    uint32_t contents[] = {3, VERTEX_SET_MAX/2, VERTEX_SET_MAX-1};
    check_contents(&set, contents, sizeof(contents)/sizeof(*contents));
  }

  ck_assert(!vertex_set_insert(&set, 3));
  ck_assert(vertex_set_remove(&set, 3));

  ck_assert(vertex_set_insert(&set, VERTEX_SET_MAX/2+28));
  ck_assert(vertex_set_insert(&set, VERTEX_SET_MAX-2));
  ck_assert(vertex_set_remove(&set, VERTEX_SET_MAX/2));
  ck_assert(!vertex_set_remove(&set, VERTEX_SET_MAX/2));
  ck_assert(vertex_set_insert(&set, VERTEX_SET_MAX/2+21));
  ck_assert(vertex_set_insert(&set, VERTEX_SET_MAX/2+23));
  ck_assert(!vertex_set_insert(&set, VERTEX_SET_MAX/2+21));
  ck_assert(!vertex_set_insert(&set, VERTEX_SET_MAX-1));

  {
    uint32_t contents[] = {VERTEX_SET_MAX/2+21, VERTEX_SET_MAX/2+23,
                           VERTEX_SET_MAX/2+28, VERTEX_SET_MAX-2,
                           VERTEX_SET_MAX-1};
    check_contents(&set, contents, sizeof(contents)/sizeof(*contents));
  }
} END_TEST

START_TEST(test_set_ops) {
  vertex_set set_a, set_b;
  vertex_set_init_empty(&set_a);
  vertex_set_init_empty(&set_b);

  for (uint32_t i = 0; i < 1024; i++) {
    vertex_set other;
    vertex_set_init_empty(&other);
    for (uint32_t j = 0; j < 20; j++)
      vertex_set_insert(&other, rand() % VERTEX_SET_MAX);

    switch (rand() % 3) {
    case 0:  /* union */
      VERTEX_SET_FOREACH(other, x)
        vertex_set_insert(&set_b, x);
      vertex_set_union(&set_a, &other);

      break;
    case 1:  /* intersection */
      vertex_set_init_empty(&set_b);
      VERTEX_SET_FOREACH(other, x) {
        if (vertex_set_contains(&set_a, x))
          vertex_set_insert(&set_b, x);
      }

      vertex_set_intersect(&set_a, &other);

      break;
    case 2: /* subtraction */
      VERTEX_SET_FOREACH(other, x)
        vertex_set_remove(&set_b, x);
      vertex_set_subtract(&set_a, &other);

      break;
    }

    VERTEX_SET_FOREACH(set_a, x)
      ck_assert(vertex_set_contains(&set_b, x));
    VERTEX_SET_FOREACH(set_b, x)
      ck_assert(vertex_set_contains(&set_a, x));
    if (vertex_set_size(&set_a) == 1)
      ck_assert(vertex_set_unit(&set_a));
    else
      ck_assert(!vertex_set_unit(&set_a));
  }
} END_TEST

Suite *vertex_set_suite(void) {
  Suite *s = suite_create("Vertex Set");

  TCase *tcase = tcase_create("Vertex Set");
  tcase_add_test(tcase, test_init_full);
  tcase_add_test(tcase, test_init_empty);
  tcase_add_test(tcase, test_init_up_to);
  tcase_add_test(tcase, test_insert_remove);
  tcase_add_test(tcase, test_set_ops);
  suite_add_tcase(s, tcase);

  return s;
}
